<template>
  <form class="el-form el-form--default el-form--label-right">
    <slot name="header"></slot>
    <slot></slot>
    <slot name="footer">
      <el-row style="text-align: right">
        <el-col>
          <el-button type="primary" @click="submitForm" :loading="loading">提交
          </el-button>
          <el-button @click="reset" :loading="loading">重置
          </el-button>
        </el-col>
      </el-row>
    </slot>
  </form>
</template>

<script lang="ts" setup>
import {ref, provide, watch, onMounted} from 'vue';
import FastForm from "@/lang/fast-form";
import Layers from "@/lang/layers"
import Asserts from "@/lang/core/asserts";
import RejectException from "./reject-exception";
import setting from "@/widget/provider/setting";


/**
 * SmartFormGroup
 *
 * 业务上，包含: “初始化”、 “序列化” 和 “表单重置” 三个功能；
 * 界面上，控制了整体的布局方式，比如，当前我们使用的是栈格系统。
 */
interface SmartFormGroup {
    // 表单数据模型，允许是对象或者数组
    model: Array | object;
    // 表单验证规则
    rules?: object;
    // 表单类型：default|form-data|request-body
    type?: 'default' | 'data' | 'body';

    // 表单默认值
    object?: Record<string, any>;
    // 附加参数，提交表单时会附加到表单内容中
    attach?: Record<string, any>;

    // 请求地址
    url?: string;
    // 请求方式
    method?: string;
    // 全局提交函数
    ajax?: Function;

    // 表单布局：垂直/水平
    inline?: boolean;

    // 触发器：用户输入变化
    trigger?: Function;
    // 成功回调函数
    succeed?: Function;
    // 提交表单前置拦截，如果不满足条件，可以阻止表单提交
    intercept?: Function;
}

// 接收 props
const props = withDefaults(defineProps<SmartFormGroup>(), {
    type: 'default', inline: false, ajax: setting.ajax
});

Asserts.notBlank(props.model, "model is required!");

// FastForm 通过事件驱动，不要调用 reactive() 函数
const fastForm = new FastForm(props.model);

if(props.rules !== undefined){
  fastForm.setRules(props.rules);
}

if (props.inline) {
    fastForm.theme = 'inline';
}

// 提供 FastForm 给所有子组件
provide('fast_form', fastForm);

// 是否正在加载
const loading = ref(false);

// 侦听 object 的变化
watch(() => props.object, (val) => {
    fastForm.setDefaultValue(val);
    fastForm.val(val);
});


/**
 * 验证表单是否有效
 */
const isValid = () => {
    return fastForm.isValid();
};

/**
 * 序列化成 FormData
 *
 * @return {FormData}
 */
const formData = () => {
    let res = fastForm.formData();
    if (props.attach) {
        for (let key in props.attach) {
            if (props.attach.hasOwnProperty(key)) {
                res.append(key, props.attach[key]);
            }
        }
    }
    return res;
};

/**
 * 序列化
 *
 * @return {object}
 */
const serialize = () => {
    let res = fastForm.serialize();
    if (props.attach) {
        for (let key in props.attach) {
            if (props.attach.hasOwnProperty(key)) {
                res[key] = props.attach[key];
            }
        }
    }
    return res;
};

/**
 * 重置表单
 *
 * 为表单渲染新的值，也可以通过设置 props.object 实现，效果是一样的
 *
 * @param obj {{}} 对象
 */
const val = (obj: any) => {
    fastForm.val(obj);
};

/**
 * 重置表单
 *
 * 并不是 cancel，而是使用默认值填充
 */
const reset = () => {
    fastForm.reset();
};

/**
 * 提交表单
 *
 * 内置提交表单的功能，使用给定的 url 和 method 进行提交
 */
const submitForm = () => {
    if (loading.value === true) {
        return;
    }
    loading.value = true;

    // default submit method
    doSubmit().then(({ret, data}) => {
        if (props.succeed !== undefined) {
            props.succeed(ret, data);
        }
    }).catch(e => {
        if (e instanceof RejectException) {
            // user cancel
            console.error(e.message);
        } else {
            // other exception
            throw e;
        }
    }).finally(() => {
        loading.value = false;
    });
};

/**
 * 提交表单函数
 *
 * @return {Promise<*>}
 */
const doSubmit = () => {
    return new Promise((resolve, reject) => {
        const data = serialize();

        if (!isValid()) {
            throw new RejectException('form is invalid!');
        }

        // 构建表单提交参数
        let opts = {method: props.method, url: props.url};
        if (props.type === 'default') {
            // 标准方式发送，功能与 form 标签功能基本一致
            opts.params = data;
        } else if (props.type === 'data') {
            // FromData方式提交
            //
            // 数据打包成 FormData 格式，以 POST 请求发送，功能与 form 标签一致，
            // 特点是数据和文件可以同时上传

            // 不要修改下面这一行代码，因为 FormData 只能由 POST 请求提交，
            // 这属于 spring 默认规则，目前，高版本的 spring 已经开放这一限制；
            opts.method = 'POST';
            opts.data = formData();
        } else if (props.type === 'body') {
            // RequestBody 方式提交
            // 可以提交一些很复杂的格式，数据会被转为 JSON 格式后提交
            opts.data = data;
        }

        // 前置拦截，进行逻辑判断，根据内容决定是否提交
        if (Layers.isNotBlank(props.intercept) && props.intercept(opts) === false) {
            throw new RejectException('user canceled!');
        }

        // 提交表单
        props.ajax(opts).then(ret => resolve(ret)).catch(err => reject(err));
    })
}

// 控件初始化
onMounted(() => {
    // 这一行尽可能延迟加载，因为异步加载的问题，
    // 如果 input 组件未加载完成，可能会产生空指针相关的异常
    if (props.object) {
        fastForm.setDefaultValue(props.object);
        fastForm.val(props.object);
    }
});

</script>